<?php
/**
 * XML节点存储对象
 *
 * 用来存储DOM中节点数据，由此可以实现DOM持久化
 * 
 * 1.0.1以后实现了对XML的较完整的查询和分析
 * 
 * 关于xpath的学习可以参考：http://www.w3schools.com/XPath/default.asp
 * 
 * @version $Id$
 * @package if
 * @subpackage module.xml
 * @since 1.0
 */
class IXmlNode extends IObject {
	private $name;
	private $value;
	private $type;
	private $parentNode;
	private $attributeMap = array();
	private $children = array();
	/**
	 * 当前DOM节点
	 *
	 * @var DOMNode
	 * @since 1.0.1
	 */
	private $domNode;
	/**
	 * Enter description here...
	 *
	 * @var IXmlDocument
	 */
	private $document;
	private $ns = array();
	private $lazy = false;
	private $childrenLoaded = false;
	
	/**
	 * 构造器
	 *
	 * @param string|DOMNode $xml XML字符串或DOM节点，v1.0.1加入
	 * @param IXmlDocument|null $document 所属文档，v1.0.1加入
	 * @param boolean $lazy 是否惰加载，该参数为true时，当前对象不会把子节点也一下子加载进来，v1.0.1加入
	 */
	function __construct($xml = null, IXmlDocument $document = null, $lazy = true) {
		$this->lazy = $lazy;
		if ($document) {
			$this->document = $document;
		}
		if ($xml) {
			if (is_string($xml)) {
				$this->load($xml);
			}
			elseif (if_is_instance_of($xml, "DOMNode")) {
				$this->loadDOM($xml);
			}
		}
	}
	
	/**
	 * 添加新属性
	 *
	 * @param string $name 属性名
	 * @param string $value 属性值
	 */
	function addAttr($name, $value) {
		$this->attributeMap[$name] = $value;
	}
	
	/**
	 * 添加子节点
	 *
	 * @param IXmlNode $childNode 新的子节点
	 */
	function addChild(IXmlNode $childNode) {
		$this->_loadSubChildren();
		$this->children[] = $childNode;
	}
	
	/**
	 * 取得属性值
	 *
	 * @param string $name 属性名
	 * @return string|null
	 */
	function attr($name) {
		if (isset($this->attributeMap[$name])) {
			return $this->attributeMap[$name];
		}
		return null;
	}
	
	/**
	 * 取得所有属性
	 *
	 * @return array
	 * @since 1.0
	 * @see attrs
	 * @deprecated 从1.0.1之后，该函数被attrs()所替代
	 */
	function attrMap() {
		return $this->attributeMap;
	}
	
	/**
	 * 取得所有属性的名值对
	 *
	 * @return array
	 * @since 1.0.1
	 */
	function attrs() {
		return $this->attrMap();
	}
	
	/**
	 * 使用名字取得对应的节点
	 *
	 * @param string $childName 子节点名
	 * @param boolean $caseSensitive 是否大小写敏感
	 * @return array
	 */
	function namedChildren($childName, $caseSensitive = true) {
		$children = array();
		foreach ($this->children() as $node) {
			$nodeName = $node->name();
			if (($caseSensitive && $nodeName === $childName) || (!$caseSensitive && strcasecmp($nodeName, $childName) == 0)) {
				$children[] = $node;
			}
		}
		return $children;
	}
	
	/**
	 * 取得所有子节点
	 *
	 * @return array
	 */
	function children() {
		$this->_loadSubChildren();
		return $this->children;
	}
	
	/**
	 * 取得一个位置上的子节点
	 *
	 * @param int $index 节点位置
	 * @return IXmlNode
	 * @since 1.0.1
	 */
	private function child($index) {
		$children = $this->children();
		return isset($children[$index]) ? $children[$index] : null;
	}
	
	/**
	 * 取得节点名
	 *
	 * @return string
	 */
	function name() {
		return $this->name;
	}
	
	/**
	 * 取得父节点
	 *
	 * @return IXmlNode
	 */
	function parent() {
		if (is_null($this->parentNode)) {
			$this->parentNode = new IXmlNode($this->domNode->parentNode, $this->document, $this->lazy);
		}
		return $this->parentNode;
	}
	
	/**
	 * 取得节点类型
	 *
	 * @return string
	 */
	function type() {
		return $this->type;
	}
	
	/**
	 * 取得节点值
	 *
	 * @return string
	 */
	function value() {
		if (is_null($this->value)) {
			$this->value = $this->domNode->nodeValue;
		}
		return $this->value;
	}
	
	/**
	 * 设置节点名
	 *
	 * @param string $name 节点名
	 */
	function setName($name) {
		$this->name = $name;
	}
	
	/**
	 * 设置节点类型
	 *
	 * @param string $type 节点类型
	 */
	function setType($type) {
		$this->type = $type;
	}
	
	/**
	 * 节点值
	 *
	 * @param string $value 节点值
	 */
	function setValue($value) {
		$this->value = $value;
	}
	
	/**
	 * 设置父节点
	 *
	 * @param IXmlNode $parentNode 父节点
	 */
	function setParent(IXmlNode $parentNode) {
		$this->parentNode = $parentNode;
	}
	
	/**
	 * 从DOM节点中加载IXmlNode节点树
	 *
	 * @param DOMNode $domNode DOM节点
	 */
	function loadDOM(DOMNode $domNode) {
		if (!$this->document && if_is_instance_of($domNode, "DOMDocument")) {
			$this->setDocument(new IXmlDocument($domNode));
		}
		$this->domNode = $domNode;
		$this->setType($domNode->nodeType);
		$this->setName($domNode->nodeName);
		if ($domNode->hasAttributes()) {
			$attributes = $domNode->attributes;
			foreach ($attributes as $attribute) {
				$this->addAttr($attribute->nodeName, $attribute->nodeValue);
			}
		}
		if (!$this->lazy && $domNode->hasChildNodes()) {
			$this->_loadSubChildren();
		}
	}
	
	private function _loadSubChildren() {
		if ($this->childrenLoaded) {
			return;
		}
		$this->childrenLoaded = true;
		$childNodes = $this->domNode->childNodes;
		foreach ($childNodes as $childNode) {
			if ($childNode->nodeType == XML_ELEMENT_NODE) {
				$child = new IXmlNode($childNode, $this->document, $this->lazy);
				$child->setParent($this);
				$this->addChild($child);
			}				
		}
	}

	/**
	 * 一下在把所有子节点加载进来
	 * 
	 * 只有在构造当前对象时$lazy参数为true才有必要调用此函数
	 *
	 * @since 1.0.1
	 */
	function loadChildren() {
		if ($this->childrenLoaded) {
			return;
		}
		$this->childrenLoaded = true;
		$childNodes = $this->domNode->childNodes;
		foreach ($childNodes as $childNode) {
			if ($childNode->nodeType == XML_ELEMENT_NODE) {
				$child = new IXmlNode($childNode, $this->document, false);
				$child->setParent($this);
				$this->addChild($child);
			}				
		}
	}
	
	/**
	 * 从XML字符串中加载节点树
	 *
	 * @param string $xmlString XML字符串
	 * @since 1.0
	 */
	function load($xmlString) {
		$dom = new DOMDocument();
		$dom->loadXML($xmlString);
		$this->setDocument(new IXmlDocument($dom));
		$this->loadDOM($dom);
	}
	
	/**
	 * 根据xpath表达式查找节点
	 *
	 * @param string|integer $expr xpath表达式或者子节点索引
	 * @return IXmlNode
	 * @since 1.0.1
	 */	
	function find($expr) {
		if (is_int($expr)) {
			$child = $this->child($expr);
			return $child ? $child : null;
		}
		$nodes = $this->findAll($expr);
		return empty($nodes) ? null : $nodes[0];
	}
	
	/**
	 * 根据xpath表达式查找节点
	 *
	 * @param string $expr 表达式
	 * @return array
	 * @since 1.0.1
	 * @throws IException  当表达式错误时将抛出一个IException异常
	 */
	function findAll($expr) {
		$xpath = new DOMXPath($this->document->dom());
		foreach ($this->document->ns() as $prefix => $uri) {
			$xpath->registerNamespace($prefix, $uri);
		}
		$nodeList = @$xpath->query($expr, $this->domNode);
		if (!$nodeList) {
			throw new IException("Invalid xpath expression: {$expr}", 0x100031);
		}
		$ret = array();
		for ($i = 0; $i < $nodeList->length; $i ++) {
			$node = $nodeList->item($i);
			$ret[] = new IXmlNode($node, $this->document, $this->lazy);
		}
		return $ret;
	}
	
	/**
	 * 查找和当前节点相邻的下一个节点
	 *
	 * @return IXmlNode
	 * @since 1.0.1
	 */
	function next() {
		$current = $this->domNode;
		while($next = $current->nextSibling) {
			if ($next->nodeType == XML_ELEMENT_NODE) {
				return new IXmlNode($next, $this->document, $this->lazy);
			}
			$current = $next;
		}
		return null;
	}
	
	/**
	 * 查找和当前节点相邻的上一个节点
	 *
	 * @return IXmlNode
	 * @since 1.0.1
	 */
	function prev() {
		$current = $this->domNode;
		while($prev = $current->previousSibling) {
			if ($prev->nodeType == XML_ELEMENT_NODE) {
				return new IXmlNode($prev, $this->document, $this->lazy);
			}
			$current = $prev;
		}
		return null;
	}	
	
	/**
	 * 将当前节点转化为XML字符串
	 *
	 * @return string
	 * @since 1.0.1
	 */
	function asXml() {
		if ($this->document) {
			return $this->document->dom()->saveXML($this->domNode);
		}
		return null;
	}
	
	/**
	 * 设置当前节点所在文档
	 *
	 * @param IXmlDocument $document 文档
	 * @since 1.0.1
	 */
	function setDocument(IXmlDocument $document) {
		$this->document = $document;
	}
	
	/**
	 * 获取当前节点所在文档
	 *
	 * @return IXmlDocument 文档
	 * @since 1.0.1
	 */
	function document() {
		return $this->document;
	}
}

?>